﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Drawing;
using NekoVampire.TwitterClient.Controls;
using XSpect;
using XSpect.Configuration;

namespace NekoVampire.TwitterClient.Configuration
{
    public sealed class UserConfiguration
    {
        public string userId, password;
        public bool addStatus, allReplies, myStatusInReplies, getOldStatus, ViewUnfollow;
        public Font unreadFont, readFont;
        public int startupTimeLineCount, getTimeLineCount, Timer;
        public Size mainFormSize;
        public List<int> columnWidth;
        public ListViewItemComparer ListViewComparer;
        public Color ForeColor, BackColor, UnfollowColor;

        #region インスタンスの生成
        private static readonly UserConfiguration instance = new UserConfiguration();

        public static UserConfiguration Configuration
        {
            get
            {
                return instance;
            }
        }
        #endregion

        private XmlConfiguration config;
        private UserConfiguration()
        {
            Assembly mainAssembly = Assembly.GetEntryAssembly();

            this.config = XmlConfiguration.Load(System.IO.Path.GetDirectoryName(mainAssembly.Location) + "\\Twite.config.xml");
            this.Load();
        }


        public void Load()
        {
            FontConverter fc = new FontConverter();
            ColorConverter cc = new ColorConverter();

            userId = config.GetValueOrDefault<String>("UserID", "");
            if (config.GetValueOrDefault<String>("Password") != null)
                try
                {
                    System.Convert.FromBase64String(config.GetValueOrDefault<String>("Password"));
                    password = Crypt.DecryptString(config.GetValueOrDefault<String>("Password"), userId);
                }
                catch (Exception)
                {
                    password = config.GetValueOrDefault<String>("Password");
                }
            else
                password = "";
            addStatus = config.GetValueOrDefault<bool>("AddStatus", false);
            unreadFont = (Font)fc.ConvertFromString(config.GetValueOrDefault<string>("UnreadFont", "MS UI Gothic, 9pt, style=Bold, Underline"));
            readFont = (Font)fc.ConvertFromString(config.GetValueOrDefault<string>("ReadFont", "MS UI Gothic, 9pt"));
            getTimeLineCount = config.GetValueOrDefault<int>("GetTimeLineCount", 100);
            startupTimeLineCount = config.GetValueOrDefault<int>("StartupTimeLineCount", 200);
            allReplies = config.GetValueOrDefault<bool>("AllReplies", true);
            myStatusInReplies = config.GetValueOrDefault<bool>("MyStatusInReplies", false);
            columnWidth = config.GetValueOrDefault<List<int>>("ColumnWidth", new List<int> { 24, 70, 490, 126 });
            mainFormSize = config.GetValueOrDefault<Size>("MainFormSize", new Size(700, 500));
            ListViewComparer = config.GetValueOrDefault<ListViewItemComparer>("ListViewComparer", new ListViewItemComparer());
            getOldStatus = config.GetValueOrDefault<bool>("GetOldStatus", false);
            ViewUnfollow = config.GetValueOrDefault<bool>("ViewUnfollow", true);
            ForeColor = (Color)cc.ConvertFromString(config.GetValueOrDefault<string>("ForeColor", "WindowText"));
            BackColor = (Color)cc.ConvertFromString(config.GetValueOrDefault<string>("BackColor", "Window"));
            UnfollowColor = (Color)cc.ConvertFromString(config.GetValueOrDefault<string>("UnfollowColor", "Blue"));
            Timer = config.GetValueOrDefault<int>("Timer", 90);
        }

        public void Save()
        {
            FontConverter fc = new FontConverter();
            ColorConverter cc = new ColorConverter();

            config["UserID"] = userId;
            config["Password"] = Crypt.EncryptString(password, userId);
            config["AddStatus"] = addStatus;
            config["UnreadFont"] = fc.ConvertToString(unreadFont);
            config["ReadFont"] = fc.ConvertToString(readFont);
            config["GetTimeLineCount"] = getTimeLineCount;
            config["StartupTimeLineCount"] = startupTimeLineCount;
            config["AllReplies"] = allReplies;
            config["MyStatusInReplies"] = myStatusInReplies;
            config["ColumnWidth"] = columnWidth;
            config["MainFormSize"] = mainFormSize;
            config["ListViewComparer"] = ListViewComparer;
            config["GetOldStatus"] = getOldStatus;
            config["ViewUnfollow"] = ViewUnfollow;
            config["ForeColor"] = cc.ConvertToString(ForeColor);
            config["BackColor"] = cc.ConvertToString(BackColor);
            config["UnfollowColor"] = cc.ConvertToString(UnfollowColor);
            config["Timer"] = Timer;

            config.Save();
        }

        #region 暗号化(thanks by DOBON.NET)
        
        private class Crypt
        {
            /// <summary>
            /// 文字列を暗号化する
            /// </summary>
            /// <param name="str">暗号化する文字列</param>
            /// <param name="key">パスワード</param>
            /// <returns>暗号化された文字列</returns>
            public static string EncryptString(string str, string key)
            {
                //文字列をバイト型配列にする
                byte[] bytesIn = System.Text.Encoding.UTF8.GetBytes(str);

                //DESCryptoServiceProviderオブジェクトの作成
                System.Security.Cryptography.DESCryptoServiceProvider des =
                    new System.Security.Cryptography.DESCryptoServiceProvider();

                //共有キーと初期化ベクタを決定
                //パスワードをバイト配列にする
                byte[] key_str = System.Text.Encoding.UTF8.GetBytes(key);

                //MD5CryptoServiceProviderオブジェクトを作成
                System.Security.Cryptography.MD5CryptoServiceProvider md5 =
                    new System.Security.Cryptography.MD5CryptoServiceProvider();

                //ハッシュ値を計算
                byte[] bytesKey = md5.ComputeHash(key_str);

                //共有キーと初期化ベクタを設定
                des.Key = ResizeBytesArray(bytesKey, des.Key.Length);
                des.IV = ResizeBytesArray(bytesKey, des.IV.Length);

                //暗号化されたデータを書き出すためのMemoryStream
                System.IO.MemoryStream msOut = new System.IO.MemoryStream();
                //DES暗号化オブジェクトの作成
                System.Security.Cryptography.ICryptoTransform desdecrypt =
                    des.CreateEncryptor();
                //書き込むためのCryptoStreamの作成
                System.Security.Cryptography.CryptoStream cryptStreem =
                    new System.Security.Cryptography.CryptoStream(msOut,
                    desdecrypt,
                    System.Security.Cryptography.CryptoStreamMode.Write);
                //書き込む
                cryptStreem.Write(bytesIn, 0, bytesIn.Length);
                cryptStreem.FlushFinalBlock();
                //暗号化されたデータを取得
                byte[] bytesOut = msOut.ToArray();

                //閉じる
                cryptStreem.Close();
                msOut.Close();

                //Base64で文字列に変更して結果を返す
                return System.Convert.ToBase64String(bytesOut);
            }

            /// <summary>
            /// 暗号化された文字列を復号化する
            /// </summary>
            /// <param name="str">暗号化された文字列</param>
            /// <param name="key">パスワード</param>
            /// <returns>復号化された文字列</returns>
            public static string DecryptString(string str, string key)
            {
                //DESCryptoServiceProviderオブジェクトの作成
                System.Security.Cryptography.DESCryptoServiceProvider des =
                    new System.Security.Cryptography.DESCryptoServiceProvider();

                //共有キーと初期化ベクタを決定
                //パスワードをバイト配列にする
                byte[] key_str = System.Text.Encoding.UTF8.GetBytes(key);
                //MD5CryptoServiceProviderオブジェクトを作成
                System.Security.Cryptography.MD5CryptoServiceProvider md5 =
                    new System.Security.Cryptography.MD5CryptoServiceProvider();
                //ハッシュ値を計算
                byte[] bytesKey = md5.ComputeHash(key_str);

                //共有キーと初期化ベクタを設定
                des.Key = ResizeBytesArray(bytesKey, des.Key.Length);
                des.IV = ResizeBytesArray(bytesKey, des.IV.Length);

                //Base64で文字列をバイト配列に戻す
                byte[] bytesIn = System.Convert.FromBase64String(str);
                //暗号化されたデータを読み込むためのMemoryStream
                System.IO.MemoryStream msIn =
                    new System.IO.MemoryStream(bytesIn);
                //DES復号化オブジェクトの作成
                System.Security.Cryptography.ICryptoTransform desdecrypt =
                    des.CreateDecryptor();
                //読み込むためのCryptoStreamの作成
                System.Security.Cryptography.CryptoStream cryptStreem =
                    new System.Security.Cryptography.CryptoStream(msIn,
                    desdecrypt,
                    System.Security.Cryptography.CryptoStreamMode.Read);

                //復号化されたデータを取得するためのStreamReader
                System.IO.StreamReader srOut =
                    new System.IO.StreamReader(cryptStreem,
                    System.Text.Encoding.UTF8);
                //復号化されたデータを取得する
                string result = srOut.ReadToEnd();

                //閉じる
                srOut.Close();
                cryptStreem.Close();
                msIn.Close();

                return result;
            }

            /// <summary>
            /// 共有キー用に、バイト配列のサイズを変更する
            /// </summary>
            /// <param name="bytes">サイズを変更するバイト配列</param>
            /// <param name="newSize">バイト配列の新しい大きさ</param>
            /// <returns>サイズが変更されたバイト配列</returns>
            private static byte[] ResizeBytesArray(byte[] bytes, int newSize)
            {
                byte[] newBytes = new byte[newSize];
                if (bytes.Length <= newSize)
                {
                    for (int i = 0; i < bytes.Length; i++)
                        newBytes[i] = bytes[i];
                }
                else
                {
                    int pos = 0;
                    for (int i = 0; i < bytes.Length; i++)
                    {
                        newBytes[pos++] ^= bytes[i];
                        if (pos >= newBytes.Length)
                            pos = 0;
                    }
                }
                return newBytes;
            }
        }
        
        #endregion
    }
}
